# This file is part of ModularBot.
# Copyright (C) 2005 Pierre Ducroquet (pierre.linux59@wanadoo.fr)

# ModularBot is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# ModularBot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with ModularBot; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import imp, time, threading, Context, rightsManager, os, zipimport, sys
from decorators import *

class CoreCommand:
	def __init__ (self, name):
		self.name = name
		self.funcs = {}	# full logic name => func name
	
	def get_func (self, context):
		# Returns a tuple (logic name, func name), or ("", "") if no logic available to handle this...
		available = []
		for (logic, func) in self.funcs.items():
			if "@" in logic:
				if logic.split("@", 1)[1] == context.channel:
					available.insert(0, (logic, func))
			else:
				available.append((logic, func))
		if len(available) > 0:
			return available[0]
		return ("", "")
	
	def add_func (self, logic, func):
		self.funcs[logic] = func
	
	def remove_logic (self, logic):
		if self.funcs.has_key(logic):
			del self.funcs[logic]
	
class Core:
	def __init__(self, bot, config):
		sys.path.append(os.path.join(os.getcwd(), "logics"))
		self.hooks = [] # This will be filled in automatically...
		self.config = config
		self.rights = rightsManager.RightsManager(config.rights)
		self.bot = bot
		self.logics = {}
		self.registers = {}
		self.events = {"joinevent": {}, "kickevent": {}, "leftevent": {}, "renameevent": {}, "modeevent": {}, "numanswer": {}, "botjoinevent": {}}
		self.globalCalls = {}
		self.commands = {} # Contains only CoreCommand objects
		self.bot.threads = {}
		self.open_logic("admin")
		self.last_arg = ""
		for module in config.logics_startup:
			self.open_logic(module)
	
	def register (self, name, caller, fonction = None):
		if fonction == None:
			for logic in self.registers.values():
				if logic.has_key(name):
					return logic[name]
			return None
		else:
			if not(self.registers.has_key(caller)):
				self.registers[caller] = {}
			self.registers[caller][name] = fonction
	
	def close_logic (self, name):
		if self.logics.has_key(name):
			self.logics[name].save()
			for event_calls in self.events.values():
				if event_calls.has_key(name):
					del event_calls[name]
			if "__del__" in dir(self.logics[name]):
				self.logics[name].__del__()
			
			if self.globalCalls.has_key(name):
				del self.globalCalls[name]
			for command in self.commands.values():
				command.remove_logic(name)
			if self.registers.has_key(name):
				del self.registers[name]
			
			del self.logics[name]
			return True
		else:
			return False
	
	def has_logic (self, name):
		return self.logics.has_key(name)
	
	def open_logic(self, name):
		if "." in name:
			return False
		self.close_logic(name)
		fullName = name
		if "@" in name:
			[name, channel] = name.split("@", 1)
		else:
			channel = ""
		if len([x for x in self.logics.keys() if (x.startswith(name + "@") and x.split("@", 1)[1] == channel)]) > 0:
			print [x for x in self.logics.keys() if (x.startswith(name + "@") and x.split("@", 1)[1] == channel)]
			return False
		result = False
		try:
			if os.path.isfile("logics/%sLogic.py" % name):
				print "Opening logic from logics/%sLogic.py" % name
				new_logic_mod = imp.load_source("", "logics/%sLogic.py" % name)
			#else:
				#print "Opening logic from logics/%s.zip" % name
				#zipdemo = zipimport.zipimporter("logics/%s.zip" % name)
				#new_logic_mod = zipdemo.load_module(name + "Logic")
			self.logics[fullName] = getattr(new_logic_mod, name + "Logic")(self)
			self.logics[fullName].name = fullName	# This is to handle "local" logics...
			self.logics[fullName].local_channel = channel
			self.logics[fullName].load()
			for event in self.events.keys():
				if hasattr(self.logics[fullName], event):
					self.events[event][fullName] = getattr (self.logics[fullName], event)
			for attr in dir(self.logics[fullName]):
				if attr.startswith("Call_"):
					commandName = attr[5:]
					if not(self.commands.has_key(commandName)):
						cmd = CoreCommand(commandName)
						self.commands[commandName] = cmd
					else:
						cmd = self.commands[commandName]
					cmd.add_func(fullName, attr)
			
			for alias in self.logics[fullName].alias.keys():
				commandName = alias
				if not(self.commands.has_key(commandName)):
					cmd = CoreCommand(commandName)
					self.commands[commandName] = cmd
				else:
					cmd = self.commands[commandName]
				cmd.add_func(fullName, self.logics[fullName].alias[alias])
			
			if "globalCall" in dir(self.logics[fullName]):
				self.globalCalls[fullName] = self.logics[fullName].globalCall
			result = True
		except:
			infos = sys.exc_info()
			sys.excepthook(infos[0], infos[1], infos[2])
		return result
	
	def event (self, event, args):
		if self.events.has_key (event):
			for call in self.events[event].values():
				try:
					call(*args)
				except:
					print "Error while executing the event", event
	
	
	def has_exec_right (self, user, logic, function):
		if function.startswith("Call_"):
			function = function[5:]
		if self.rights.logged_in(user):
			print "The user is logged in, ok"
			return self.rights.has_right(user, "module:%s" % logic) or self.rights.has_right(user, "function:%s" % function) or self.rights.is_admin(user)
		else:
			return False
	
	def exec_commande (self, ctxte, command):
		command = command.strip()
		if command == "":
			return
		if self.commands.has_key(command):
			cmd = self.commands[command]
			(logicName, funcName) = cmd.get_func(ctxte)
			if logicName == "" or funcName == "":
				ctxte.say("Command %s not available here" % command)
			logic = self.logics[logicName]
			cmd_func = getattr(logic, funcName)
			admin = self.has_exec_right(ctxte.user, logicName, funcName)
			if hasattr(cmd_func, "is_admin") and not admin :
				ctxte.say("The command %s is an admin command" % funcName)
				return
			ctxte.cmd_func = cmd_func
			ctxte.is_admin = admin
			ctxte.launch()
		else:
			ctxte.say("Command %s not found" % command)
	
	
	# Hooks return "" for an error, nothing when the hook is not concerned, the message without the hook when it applies.
	@core_hook("time")
	def hook_time (self, ctxte, message):
		ctxte.timing = True
		return message[4:].strip()
	
	@core_hook("notice")
	def hook_notice (self, ctxte, message):
		ctxte.noticing = True
		return message[6:].strip()
	
	@core_hook("grep")
	def hook_grep (self, ctxte, message):
		message = message[5:].strip()
		if not message.startswith('"'):
			ctxte.say('Usage : grep "txt" command')
			return ""
		else:
			i = 1
			while i < len(message):
				if message[i] == '"':
					break
				i += 1
			if i == len(message):
				ctxte.say('Usage : grep "txt" command')
				return ""
			else:
				criteria = message[1:i]
				command = message[i+1:].strip()
				if len(command) == 0:
					ctxte.say('Usage : grep "txt" command')
				else:
					ctxte.grep = criteria
					return command

	def answer(self, ctxte):
		for (logic, call) in self.globalCalls.items():
			if "@" in logic:
				if logic.split("@", 1)[1] == ctxte.channel:
					call(ctxte)
			else:
				call(ctxte)
		if ctxte.message.startswith(self.bot.nickname) and not ctxte.stop:
			# Set the right message value now
			ctxte.message = ctxte.message[len(self.bot.nickname) + 1:].strip()
			message = ctxte.message
			if len(self.hooks) == 0:
				self.hooks = [getattr(self, fct) for fct in dir(self) if hasattr(getattr(self, fct), "is_hook")]
			hooks = self.hooks[:] # We make a copy of self.hooks, because we'll use remove in order to prevent dual execution of a hook
			while 1:
				tocontinue = False
				for hook in hooks:
					if message.startswith(hook.hook_name):
						result = hook(ctxte, message)
						if result == "":
							return
						else:
							tocontinue = True
							hooks.remove(hook)
							message = result
							ctxte.message = message
				if not tocontinue or len(hooks) == 0:
					break
			for dual_linker in ["&&", " & "]:
				while dual_linker in message:
					message = message.replace(dual_linker, "&")
			if " " in message:
				[command, ctxte.command_args] = message.split(" ",1)
				if ctxte.command_args in ["*", "!*"]:
					ctxte.command_args = self.last_arg
				else:
					self.last_arg = ctxte.command_args
			else:
				command = message
				ctxte.command_args = ""
			if "&" in command:
				commands = command.split("&")
				for command in commands:
					print "executing", command
					self.exec_commande(ctxte, command)
			else:
				print "executing", command
				self.exec_commande(ctxte, command)
